﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

namespace System.Threading.Actors.Languages
{
    public class PythonLanguagePackage : ILanguagePackage
    {
        private readonly string m_methodKeyPrefix = "$$py:";
        private readonly ScriptEngine m_pyEngine;
        private readonly ScriptScope m_pyScope;
        private readonly IActorState m_actorState;
        //private readonly PythonStateWrapper m_wrappedState;

        public PythonLanguagePackage(IActorState state)
        {
            m_actorState = state;
            m_pyEngine = Python.CreateEngine();
            m_pyScope = m_pyEngine.CreateScope();
            //m_wrappedState = new PythonStateWrapper(state);
            //m_pyScope.SetVariable("__actorState", new PythonStateWrapper(state));
            //m_pyScope.ImportModule("json");
            m_pyScope.ImportModule("clr");
        }

        // Used during CallMethod to determine whether this language pack owns the method
        public bool OwnsMethod(string methodName)
        {
            object unused;
            return m_actorState.TryGet(m_methodKeyPrefix + methodName, out unused);
        }

        // Used during replication to determine whether this method needs to be added on
        // the replica.
        public bool OwnsKey(string methodKey)
        {
            return methodKey.StartsWith(m_methodKeyPrefix);
        }

        public void AddMethod(string methodName, string methodImpl)
        {
            m_pyEngine.CreateScriptSourceFromString(methodImpl).Compile().Execute(m_pyScope);
            m_actorState.Set(m_methodKeyPrefix + methodName, methodImpl);
        }

        public void AddMethod(string methodImpl)
        {
            m_pyEngine.CreateScriptSourceFromString(methodImpl).Compile().Execute(m_pyScope);
        }

        public object CallMethod(string methodName, params object[] methodArgs)
        {
            object tempObject;
            if (m_actorState.TryGet(m_methodKeyPrefix + methodName, out tempObject))
            {
                // This is a python method.
                var func = m_pyEngine.Execute(methodName, m_pyScope); // returns a dynamic
                var numArgs = (methodArgs == null) ? 0 : methodArgs.Length;
                object result = null;
                // There is apparently no way to populate an args list separately, so I'm reduced to the below
                switch (numArgs)
                {
                    case 0: result = func(m_actorState); break;
                    case 1: result = func(m_actorState, methodArgs[0]); break;
                    case 2: result = func(m_actorState, methodArgs[0], methodArgs[1]); break;
                    case 3: result = func(m_actorState, methodArgs[0], methodArgs[1], methodArgs[2]); break;
                    case 4: result = func(m_actorState, methodArgs[0], methodArgs[1], methodArgs[2], methodArgs[3]); break;
                    case 5: result = func(m_actorState, methodArgs[0], methodArgs[1], methodArgs[2], methodArgs[3], methodArgs[4]); break;
                    case 6: result = func(m_actorState, methodArgs[0], methodArgs[1], methodArgs[2], methodArgs[3], methodArgs[4], methodArgs[5]); break;
                    case 7: result = func(m_actorState, methodArgs[0], methodArgs[1], methodArgs[2], methodArgs[3], methodArgs[4], methodArgs[5], methodArgs[6]); break;
                    case 8: result = func(m_actorState, methodArgs[0], methodArgs[1], methodArgs[2], methodArgs[3], methodArgs[4], methodArgs[5], methodArgs[6], methodArgs[7]); break;
                    default: 
                        // Kludgy hack for numArgs > 8

                        // Build a command string, and store parameters as global variables
                        // Hacky, but should work, since we don't expect multiple commands to be executed at once
                        StringBuilder sb = new StringBuilder();
                        sb.AppendFormat("{0}(__actorState", methodName);
                        m_pyScope.SetVariable("__actorState", m_actorState);
                        for (int i = 0; i < numArgs; i++)
                        {
                            var arg = methodArgs[i];
                            var argName = "__arg" + i;
                            m_pyScope.SetVariable(argName, arg);
                            sb.AppendFormat(",{0}", argName);
                        }
                        sb.Append(")");
                        var command = sb.ToString();

                        // Execute the command
                        result = m_pyEngine.Execute(command, m_pyScope);
                        
                        // Clean up
                        m_pyScope.RemoveVariable("__actorState");
                        for (int i = 0; i < numArgs; i++) m_pyScope.RemoveVariable("__arg" + i);

                        break;
                }

                return result;

            }
            else throw new InvalidOperationException("Attempted to call non-existent PY method " + methodName);
        }

    }

    // I thought that I would need this to substitute Contains for TryGet, but I think that
    // TryGet will work fine with IronPython.
    //public class PythonStateWrapper
    //{
    //    private readonly IActorState m_state;
    //    public PythonStateWrapper(IActorState state)
    //    {
    //        m_state = state;
    //    }


    //    public void Set(string key, object value)
    //    {
    //        m_state.Set(key, value);
    //    }

    //    public object Get(string key)
    //    {
    //        return m_state.Get(key);
    //    }

    //    public bool TryGet(string key, out object value)
    //    {
    //        return m_state.TryGet(key, out value);
    //    }

    //    public bool Contains(string key)
    //    {
    //        object temp;
    //        return m_state.TryGet(key, out temp);
    //    }

    //    public void Remove(string key)
    //    {
    //        m_state.Remove(key);
    //    }

    //    public IEnumerable<string> GetAllKeysWithPrefix(string keyPrefix)
    //    {
    //        return m_state.GetAllKeysWithPrefix(keyPrefix);
    //    }

    //    public object CallMethod(string methodName, object[] args)
    //    {
    //        return m_state.CallMethod(methodName, args);
    //    }

    //    public void Publish(string eventType, object eventPayload)
    //    {
    //        m_state.Publish(eventType, eventPayload);
    //    }

    //    public void Publish(string eventType, object eventPayload, params object[] eventPayloadQualifiers)
    //    {
    //        m_state.Publish(eventType, eventPayload, eventPayloadQualifiers);
    //    }

    //    public IActorProxy GetActorProxy(string externalActorName)
    //    {
    //        return m_state.GetActorProxy(externalActorName);
    //    }

    //    public string GetId()
    //    {
    //        return m_state.GetId();
    //    }

    //    public bool CreateActor(string actorName, string creationString)
    //    {
    //        return m_state.CreateActor(actorName, creationString);
    //    }

    //    public void DeleteActor(string actorName)
    //    {
    //        m_state.DeleteActor(actorName);
    //    }

    //}
}
